﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace Kang.ExtendMethod
{
    public class Url
    {
        private string _originalString;

        private bool _parsed;

        private string _scheme;

        private string _userInfo;

        private string _host;

        private List<string> _pathSegments;

        private QueryParamCollection _queryParams;

        private string _fragment;

        private int? _port;

        private bool _leadingSlash;

        private bool _trailingSlash;

        private const int MAX_URL_LENGTH = 65519;

        //
        // 摘要:
        //     The scheme of the URL, i.e. "http". Does not include ":" delimiter. Empty string
        //     if the URL is relative.
        public string Scheme
        {
            get
            {
                return EnsureParsed()._scheme;
            }
            set
            {
                EnsureParsed()._scheme = value;
            }
        }

        //
        // 摘要:
        //     i.e. "user:pass" in "https://user:pass@www.site.com". Empty string if not present.
        public string UserInfo
        {
            get
            {
                return EnsureParsed()._userInfo;
            }
            set
            {
                EnsureParsed()._userInfo = value;
            }
        }

        //
        // 摘要:
        //     i.e. "www.site.com" in "https://www.site.com:8080/path". Does not include user
        //     info or port.
        public string Host
        {
            get
            {
                return EnsureParsed()._host;
            }
            set
            {
                EnsureParsed()._host = value;
            }
        }

        //
        // 摘要:
        //     Port number of the URL. Null if not explicitly specified.
        public int? Port
        {
            get
            {
                return EnsureParsed()._port;
            }
            set
            {
                EnsureParsed()._port = value;
            }
        }

        //
        // 摘要:
        //     i.e. "www.site.com:8080" in "https://www.site.com:8080/path". Includes both user
        //     info and port, if included.
        public string Authority
        {
            get
            {
                object[] obj = new object[5] { UserInfo, null, null, null, null };
                string userInfo = UserInfo;
                obj[1] = ((userInfo != null && userInfo.Length > 0) ? "@" : "");
                obj[2] = Host;
                obj[3] = (Port.HasValue ? ":" : "");
                obj[4] = Port;
                return string.Concat(obj);
            }
        }

        //
        // 摘要:
        //     i.e. "https://www.site.com:8080" in "https://www.site.com:8080/path" (everything
        //     before the path).
        public string Root
        {
            get
            {
                string scheme = Scheme;
                string scheme2 = Scheme;
                string obj = ((scheme2 != null && scheme2.Length > 0) ? ":" : "");
                string authority = Authority;
                return scheme + obj + ((authority != null && authority.Length > 0) ? "//" : "") + Authority;
            }
        }

        //
        // 摘要:
        //     i.e. "/path" in "https://www.site.com/path". Empty string if not present. Leading
        //     and trailing "/" retained exactly as specified by user.
        public string Path
        {
            get
            {
                EnsureParsed();
                return (_leadingSlash ? "/" : "") + string.Join("/", PathSegments) + ((_trailingSlash && PathSegments.Any()) ? "/" : "");
            }
            set
            {
                PathSegments.Clear();
                _trailingSlash = false;
                if (string.IsNullOrEmpty(value))
                {
                    _leadingSlash = false;
                }
                else if (value == "/")
                {
                    _leadingSlash = true;
                }
                else
                {
                    AppendPathSegment(value ?? "");
                }
            }
        }

        //
        // 摘要:
        //     The "/"-delimited segments of the path, not including leading or trailing "/"
        //     characters.
        public IList<string> PathSegments => EnsureParsed()._pathSegments;

        //
        // 摘要:
        //     i.e. "x=1&y=2" in "https://www.site.com/path?x=1&y=2". Does not include "?".
        public string Query
        {
            get
            {
                return QueryParams.ToString();
            }
            set
            {
                EnsureParsed()._queryParams = new QueryParamCollection(value);
            }
        }

        //
        // 摘要:
        //     Query parsed to name/value pairs.
        public QueryParamCollection QueryParams => EnsureParsed()._queryParams;

        //
        // 摘要:
        //     i.e. "frag" in "https://www.site.com/path?x=y#frag". Does not include "#".
        public string Fragment
        {
            get
            {
                return EnsureParsed()._fragment;
            }
            set
            {
                EnsureParsed()._fragment = value;
            }
        }

        //
        // 摘要:
        //     True if URL does not start with a non-empty scheme. i.e. true for "https://www.site.com",
        //     false for "//www.site.com".
        public bool IsRelative => string.IsNullOrEmpty(Scheme);

        //
        // 摘要:
        //     True if Url is absolute and scheme is https or wss.
        public bool IsSecureScheme
        {
            get
            {
                if (!IsRelative)
                {
                    if (!Scheme.OrdinalEquals("https", ignoreCase: true))
                    {
                        return Scheme.OrdinalEquals("wss", ignoreCase: true);
                    }

                    return true;
                }

                return false;
            }
        }

        //
        // 摘要:
        //     Constructs a Url object from a string.
        //
        // 参数:
        //   baseUrl:
        //     The URL to use as a starting point.
        public Url(string baseUrl = null)
        {
            _originalString = baseUrl?.Trim();
        }

        //
        // 摘要:
        //     Constructs a Url object from a System.Uri.
        //
        // 参数:
        //   uri:
        //     The System.Uri (required)
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     uri is null.
        public Url(Uri uri)
        {
            _originalString = (uri ?? throw new ArgumentNullException("uri")).OriginalString;
            ParseInternal(uri);
        }

        //
        // 摘要:
        //     Parses a URL string into a Flurl.Url object.
        public static Url Parse(string url)
        {
            return new Url(url);
        }

        private Url EnsureParsed()
        {
            if (!_parsed)
            {
                ParseInternal();
            }

            return this;
        }

        private void ParseInternal(Uri uri = null)
        {
            _parsed = true;
            uri = uri ?? new Uri(_originalString ?? "", UriKind.RelativeOrAbsolute);
            if (uri.IsAbsoluteUri)
            {
                _scheme = uri.Scheme;
                _userInfo = uri.UserInfo;
                _host = uri.Host;
                _port = ((_originalString?.OrdinalStartsWith($"{Root}:{uri.Port}", ignoreCase: true) ?? false) ? new int?(uri.Port) : null);
                _pathSegments = new List<string>();
                if (uri.AbsolutePath.Length > 0 && uri.AbsolutePath != "/")
                {
                    AppendPathSegment(uri.AbsolutePath);
                }

                _queryParams = new QueryParamCollection(uri.Query);
                _fragment = uri.Fragment.TrimStart(new char[1] { '#' });
                _leadingSlash = uri.OriginalString.OrdinalStartsWith(Root + "/", ignoreCase: true);
                _trailingSlash = _pathSegments.Any() && uri.AbsolutePath.OrdinalEndsWith("/");
                bool flag = uri.OriginalString.OrdinalStartsWith(Scheme + "://", ignoreCase: true);
                if (flag && Authority.Length == 0 && PathSegments.Any())
                {
                    _host = _pathSegments[0];
                    _pathSegments.RemoveAt(0);
                }
                else if (!flag && Authority.Length > 0)
                {
                    _pathSegments.Insert(0, Authority);
                    _userInfo = "";
                    _host = "";
                    _port = null;
                }
            }
            else if (uri.OriginalString.OrdinalStartsWith("//"))
            {
                ParseInternal(new Uri("http:" + uri.OriginalString));
                _scheme = "";
            }
            else if (uri.OriginalString.OrdinalStartsWith("/"))
            {
                ParseInternal(new Uri("http://temp.com" + uri.OriginalString));
                _scheme = "";
                _host = "";
                _leadingSlash = true;
            }
            else
            {
                ParseInternal(new Uri("http://temp.com/" + uri.OriginalString));
                _scheme = "";
                _host = "";
                _leadingSlash = false;
            }
        }

        //
        // 摘要:
        //     Parses a URL query to a QueryParamCollection.
        //
        // 参数:
        //   query:
        //     The URL query to parse.
        public static QueryParamCollection ParseQueryParams(string query)
        {
            return new QueryParamCollection(query);
        }

        //
        // 摘要:
        //     Splits the given path into segments, encoding illegal characters, "?", and "#".
        //
        // 参数:
        //   path:
        //     The path to split.
        public static IEnumerable<string> ParsePathSegments(string path)
        {
            string[] segments = EncodeIllegalCharacters(path).Replace("?", "%3F").Replace("#", "%23").Split(new char[1] { '/' });
            if (segments.Any())
            {
                int num = ((segments.First().Length <= 0) ? 1 : 0);
                int count = segments.Length - ((segments.Last().Length <= 0) ? 1 : 0);
                for (int i = num; i < count; i++)
                {
                    yield return segments[i];
                }
            }
        }

        //
        // 摘要:
        //     Appends a segment to the URL path, ensuring there is one and only one '/' character
        //     as a separator.
        //
        // 参数:
        //   segment:
        //     The segment to append
        //
        //   fullyEncode:
        //     If true, URL-encodes reserved characters such as '/', '+', and '%'. Otherwise,
        //     only encodes strictly illegal characters (including '%' but only when not followed
        //     by 2 hex characters).
        //
        // 返回结果:
        //     the Url object with the segment appended
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     segment is null.
        public Url AppendPathSegment(object segment, bool fullyEncode = false)
        {
            if (segment == null)
            {
                throw new ArgumentNullException("segment");
            }

            EnsureParsed();
            if (fullyEncode)
            {
                PathSegments.Add(Uri.EscapeDataString(segment.ToInvariantString()));
                _trailingSlash = false;
            }
            else
            {
                string text = segment.ToInvariantString();
                foreach (string item in ParsePathSegments(text))
                {
                    PathSegments.Add(item);
                }

                _trailingSlash = text.EndsWith("/");
            }

            _leadingSlash |= !IsRelative;
            return this;
        }

        //
        // 摘要:
        //     Appends multiple segments to the URL path, ensuring there is one and only one
        //     '/' character as a seperator.
        //
        // 参数:
        //   segments:
        //     The segments to append
        //
        // 返回结果:
        //     the Url object with the segments appended
        public Url AppendPathSegments(params object[] segments)
        {
            foreach (object segment in segments)
            {
                AppendPathSegment(segment);
            }

            return this;
        }

        //
        // 摘要:
        //     Appends multiple segments to the URL path, ensuring there is one and only one
        //     '/' character as a seperator.
        //
        // 参数:
        //   segments:
        //     The segments to append
        //
        // 返回结果:
        //     the Url object with the segments appended
        public Url AppendPathSegments(IEnumerable<object> segments)
        {
            foreach (object segment in segments)
            {
                AppendPathSegment(segment);
            }

            return this;
        }

        //
        // 摘要:
        //     Removes the last path segment from the URL.
        //
        // 返回结果:
        //     The Url object.
        public Url RemovePathSegment()
        {
            if (PathSegments.Any())
            {
                PathSegments.RemoveAt(PathSegments.Count - 1);
            }

            return this;
        }

        //
        // 摘要:
        //     Removes the entire path component of the URL, including the leading slash.
        //
        // 返回结果:
        //     The Url object.
        public Url RemovePath()
        {
            PathSegments.Clear();
            _leadingSlash = (_trailingSlash = false);
            return this;
        }

        //
        // 摘要:
        //     Adds a parameter to the query, overwriting the value if name exists.
        //
        // 参数:
        //   name:
        //     Name of query parameter
        //
        //   value:
        //     Value of query parameter
        //
        //   nullValueHandling:
        //     Indicates how to handle null values. Defaults to Remove (any existing)
        //
        // 返回结果:
        //     The Url object with the query parameter added
        public Url SetQueryParam(string name, object value, NullValueHandling nullValueHandling = NullValueHandling.Remove)
        {
            QueryParams.AddOrReplace(name, value, isEncoded: false, nullValueHandling);
            return this;
        }

        //
        // 摘要:
        //     Adds a parameter to the query, overwriting the value if name exists.
        //
        // 参数:
        //   name:
        //     Name of query parameter
        //
        //   value:
        //     Value of query parameter
        //
        //   isEncoded:
        //     Set to true to indicate the value is already URL-encoded
        //
        //   nullValueHandling:
        //     Indicates how to handle null values. Defaults to Remove (any existing)
        //
        // 返回结果:
        //     The Url object with the query parameter added
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     name is null.
        public Url SetQueryParam(string name, string value, bool isEncoded = false, NullValueHandling nullValueHandling = NullValueHandling.Remove)
        {
            QueryParams.AddOrReplace(name, value, isEncoded, nullValueHandling);
            return this;
        }

        //
        // 摘要:
        //     Adds a parameter without a value to the query, removing any existing value.
        //
        // 参数:
        //   name:
        //     Name of query parameter
        //
        // 返回结果:
        //     The Url object with the query parameter added
        public Url SetQueryParam(string name)
        {
            QueryParams.AddOrReplace(name, null, isEncoded: false, NullValueHandling.NameOnly);
            return this;
        }

        //
        // 摘要:
        //     Parses values (usually an anonymous object or dictionary) into name/value pairs
        //     and adds them to the query, overwriting any that already exist.
        //
        // 参数:
        //   values:
        //     Typically an anonymous object, ie: new { x = 1, y = 2 }
        //
        //   nullValueHandling:
        //     Indicates how to handle null values. Defaults to Remove (any existing)
        //
        // 返回结果:
        //     The Url object with the query parameters added
        public Url SetQueryParams(object values, NullValueHandling nullValueHandling = NullValueHandling.Remove)
        {
            if (values == null)
            {
                return this;
            }

            string text = values as string;
            if (text != null)
            {
                return SetQueryParam(text);
            }

            foreach (var item in values.ToKeyValuePairs())
            {
                SetQueryParam(item.Key, item.Value, nullValueHandling);
            }

            return this;
        }

        //
        // 摘要:
        //     Adds multiple parameters without values to the query.
        //
        // 参数:
        //   names:
        //     Names of query parameters.
        //
        // 返回结果:
        //     The Url object with the query parameter added
        public Url SetQueryParams(IEnumerable<string> names)
        {
            if (names == null)
            {
                return this;
            }

            foreach (string item in names.Where((string n) => !string.IsNullOrEmpty(n)))
            {
                SetQueryParam(item);
            }

            return this;
        }

        //
        // 摘要:
        //     Adds multiple parameters without values to the query.
        //
        // 参数:
        //   names:
        //     Names of query parameters
        //
        // 返回结果:
        //     The Url object with the query parameter added.
        public Url SetQueryParams(params string[] names)
        {
            return SetQueryParams((IEnumerable<string>)names);
        }

        //
        // 摘要:
        //     Removes a name/value pair from the query by name.
        //
        // 参数:
        //   name:
        //     Query string parameter name to remove
        //
        // 返回结果:
        //     The Url object with the query parameter removed
        public Url RemoveQueryParam(string name)
        {
            QueryParams.Remove(name);
            return this;
        }

        //
        // 摘要:
        //     Removes multiple name/value pairs from the query by name.
        //
        // 参数:
        //   names:
        //     Query string parameter names to remove
        //
        // 返回结果:
        //     The Url object.
        public Url RemoveQueryParams(params string[] names)
        {
            foreach (string name in names)
            {
                QueryParams.Remove(name);
            }

            return this;
        }

        //
        // 摘要:
        //     Removes multiple name/value pairs from the query by name.
        //
        // 参数:
        //   names:
        //     Query string parameter names to remove
        //
        // 返回结果:
        //     The Url object with the query parameters removed
        public Url RemoveQueryParams(IEnumerable<string> names)
        {
            foreach (string name in names)
            {
                QueryParams.Remove(name);
            }

            return this;
        }

        //
        // 摘要:
        //     Removes the entire query component of the URL.
        //
        // 返回结果:
        //     The Url object.
        public Url RemoveQuery()
        {
            QueryParams.Clear();
            return this;
        }

        //
        // 摘要:
        //     Set the URL fragment fluently.
        //
        // 参数:
        //   fragment:
        //     The part of the URL after #
        //
        // 返回结果:
        //     The Url object with the new fragment set
        public Url SetFragment(string fragment)
        {
            Fragment = fragment ?? "";
            return this;
        }

        //
        // 摘要:
        //     Removes the URL fragment including the #.
        //
        // 返回结果:
        //     The Url object with the fragment removed
        public Url RemoveFragment()
        {
            return SetFragment("");
        }

        //
        // 摘要:
        //     Resets the URL to its root, including the scheme, any user info, host, and port
        //     (if specified).
        //
        // 返回结果:
        //     The Url object trimmed to its root.
        public Url ResetToRoot()
        {
            PathSegments.Clear();
            QueryParams.Clear();
            Fragment = "";
            _leadingSlash = false;
            _trailingSlash = false;
            return this;
        }

        //
        // 摘要:
        //     Resets the URL to its original state as set in the constructor.
        public Url Reset()
        {
            if (_parsed)
            {
                _scheme = null;
                _userInfo = null;
                _host = null;
                _port = null;
                _pathSegments = null;
                _queryParams = null;
                _fragment = null;
                _leadingSlash = false;
                _trailingSlash = false;
                _parsed = false;
            }

            return this;
        }

        //
        // 摘要:
        //     Creates a copy of this Url.
        public Url Clone()
        {
            return new Url(this);
        }

        //
        // 摘要:
        //     Converts this Url object to its string representation.
        //
        // 参数:
        //   encodeSpaceAsPlus:
        //     Indicates whether to encode spaces with the "+" character instead of "%20"
        public string ToString(bool encodeSpaceAsPlus)
        {
            if (!_parsed)
            {
                return _originalString ?? "";
            }

            string[] obj = new string[6]
            {
                Root,
                encodeSpaceAsPlus ? Path.Replace("%20", "+") : Path,
                QueryParams.Any() ? "?" : "",
                QueryParams.ToString(encodeSpaceAsPlus),
                null,
                null
            };
            string fragment = Fragment;
            obj[4] = ((fragment != null && fragment.Length > 0) ? "#" : "");
            obj[5] = Fragment;
            return string.Concat(obj).Trim();
        }

        //
        // 摘要:
        //     Converts this Url object to its string representation.
        public override string ToString()
        {
            return ToString(encodeSpaceAsPlus: false);
        }

        //
        // 摘要:
        //     Converts this Url object to System.Uri
        //
        // 返回结果:
        //     The System.Uri object
        public Uri ToUri()
        {
            return new Uri((string)this, UriKind.RelativeOrAbsolute);
        }

        //
        // 摘要:
        //     Implicit conversion from Url to String.
        //
        // 参数:
        //   url:
        //     The Url object
        //
        // 返回结果:
        //     The string
        public static implicit operator string(Url url)
        {
            return url?.ToString();
        }

        //
        // 摘要:
        //     Implicit conversion from String to Url.
        //
        // 参数:
        //   url:
        //     The String representation of the URL
        //
        // 返回结果:
        //     The string
        public static implicit operator Url(string url)
        {
            return new Url(url);
        }

        //
        // 摘要:
        //     Implicit conversion from System.Uri to Flurl.Url.
        //
        // 返回结果:
        //     The string
        public static implicit operator Url(Uri uri)
        {
            return new Url(uri.ToString());
        }

        //
        // 摘要:
        //     True if obj is an instance of Url and its string representation is equal to this
        //     instance's string representation.
        //
        // 参数:
        //   obj:
        //     The object to compare to this instance.
        public override bool Equals(object obj)
        {
            Url url = obj as Url;
            if (url != null)
            {
                return ToString().Equals(url.ToString());
            }

            return false;
        }

        //
        // 摘要:
        //     Returns the hashcode for this Url.
        public override int GetHashCode()
        {
            return ToString().GetHashCode();
        }

        //
        // 摘要:
        //     Basically a Path.Combine for URLs. Ensures exactly one '/' separates each segment,
        //     and exactly on '&' separates each query parameter. URL-encodes illegal characters
        //     but not reserved characters.
        //
        // 参数:
        //   parts:
        //     URL parts to combine.
        public static string Combine(params string[] parts)
        {
            if (parts == null)
            {
                throw new ArgumentNullException("parts");
            }

            string text = "";
            bool flag = false;
            bool flag2 = false;
            foreach (string text2 in parts)
            {
                if (!string.IsNullOrEmpty(text2))
                {
                    text = ((!text.EndsWith("?") && !text2.StartsWith("?")) ? ((text.EndsWith("#") || text2.StartsWith("#")) ? CombineEnsureSingleSeparator(text, text2, '#') : (flag2 ? (text + text2) : ((!flag) ? CombineEnsureSingleSeparator(text, text2, '/') : CombineEnsureSingleSeparator(text, text2, '&')))) : CombineEnsureSingleSeparator(text, text2, '?'));
                    if (text2.Contains("#"))
                    {
                        flag = false;
                        flag2 = true;
                    }
                    else if (!flag2 && text2.Contains("?"))
                    {
                        flag = true;
                    }
                }
            }

            return EncodeIllegalCharacters(text);
            static string CombineEnsureSingleSeparator(string a, string b, char separator)
            {
                if (string.IsNullOrEmpty(a))
                {
                    return b;
                }

                if (string.IsNullOrEmpty(b))
                {
                    return a;
                }

                return a.TrimEnd(new char[1] { separator }) + separator + b.TrimStart(new char[1] { separator });
            }
        }

        //
        // 摘要:
        //     Decodes a URL-encoded string.
        //
        // 参数:
        //   s:
        //     The URL-encoded string.
        //
        //   interpretPlusAsSpace:
        //     If true, any '+' character will be decoded to a space.
        public static string Decode(string s, bool interpretPlusAsSpace)
        {
            if (string.IsNullOrEmpty(s))
            {
                return s;
            }

            return Uri.UnescapeDataString(interpretPlusAsSpace ? s.Replace("+", " ") : s);
        }

        //
        // 摘要:
        //     URL-encodes a string, including reserved characters such as '/' and '?'.
        //
        // 参数:
        //   s:
        //     The string to encode.
        //
        //   encodeSpaceAsPlus:
        //     If true, spaces will be encoded as + signs. Otherwise, they'll be encoded as
        //     %20.
        //
        // 返回结果:
        //     The encoded URL.
        public static string Encode(string s, bool encodeSpaceAsPlus = false)
        {
            if (string.IsNullOrEmpty(s))
            {
                return s;
            }

            if (s.Length > 65519)
            {
                string[] array = new string[(int)Math.Ceiling((double)s.Length / 65519.0)];
                for (int i = 0; i < array.Length; i++)
                {
                    int num = i * 65519;
                    int length = Math.Min(65519, s.Length - num);
                    array[i] = Uri.EscapeDataString(s.Substring(num, length));
                }

                s = string.Concat(array);
            }
            else
            {
                s = Uri.EscapeDataString(s);
            }

            if (!encodeSpaceAsPlus)
            {
                return s;
            }

            return s.Replace("%20", "+");
        }

        //
        // 摘要:
        //     URL-encodes characters in a string that are neither reserved nor unreserved.
        //     Avoids encoding reserved characters such as '/' and '?'. Avoids encoding '%'
        //     if it begins a %-hex-hex sequence (i.e. avoids double-encoding).
        //
        // 参数:
        //   s:
        //     The string to encode.
        //
        //   encodeSpaceAsPlus:
        //     If true, spaces will be encoded as + signs. Otherwise, they'll be encoded as
        //     %20.
        //
        // 返回结果:
        //     The encoded URL.
        public static string EncodeIllegalCharacters(string s, bool encodeSpaceAsPlus = false)
        {
            if (string.IsNullOrEmpty(s))
            {
                return s;
            }

            if (encodeSpaceAsPlus)
            {
                s = s.Replace(" ", "+");
            }

            if (!s.Contains("%"))
            {
                return Uri.EscapeUriString(s);
            }

            return Regex.Replace(s, "(.*?)((%[0-9A-Fa-f]{2})|$)", delegate (Match c)
            {
                string value = c.Groups[1].Value;
                return string.Concat(str1: c.Groups[2].Value, str0: Uri.EscapeUriString(value));
            });
        }

        //
        // 摘要:
        //     Checks if a string is a well-formed absolute URL.
        //
        // 参数:
        //   url:
        //     The string to check
        //
        // 返回结果:
        //     true if the string is a well-formed absolute URL
        public static bool IsValid(string url)
        {
            if (url != null)
            {
                return Uri.IsWellFormedUriString(url, UriKind.Absolute);
            }

            return false;
        }
    }
}
